using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using OpenWaves.Caching;
using ThreadState = System.Threading.ThreadState;

namespace OpenWaves.UnitTests.Caching
{
    internal class TestThreadsCoordinator : IThreadPool
    {
        private readonly Clock clock = new Clock();
        private readonly ConcurrentBag<Thread> threads = new ConcurrentBag<Thread>();

        public void RunThreads(params Action[] actions)
        {
            var accumulatedExceptions = new ConcurrentBag<Exception>();

            var threads = actions.Select(a => new Thread(() =>
            {
                                                                   try
                                                                   {
                                                                       a();
                                                                   }
                                                                   catch (Exception e)
                                                                   {
                                                                       accumulatedExceptions.Add(e);
                                                                   }
            })).ToList();

            foreach (var thread in threads)
            {
                this.threads.Add(thread);
                thread.Start();
            }

            do
            {
                if (this.threads.All(t => t.ThreadState == ThreadState.WaitSleepJoin || t.ThreadState == ThreadState.Stopped))
                {
                    this.clock.Tick();
                }

                Thread.Sleep(10);

            } while (this.threads.Any(t => t.ThreadState != ThreadState.Stopped));

            if (accumulatedExceptions.Count > 0)
                throw new AggregateException(accumulatedExceptions);
        }

        public IClock Clock
        {
            get { return this.clock; }
        }

        public void WaitForTick(int tick)
        {
            this.clock.WaitForTick(tick);
        }

        public void QueueWorkItem(Action action)
        {
            var thread = new Thread(() => action());
            this.threads.Add(thread);
            thread.Start();
        }
    }

    internal class Clock : IClock
    {
        private int tick;

        public void Tick()
        {
            lock(this)
            {
                Debug.WriteLine("{0}: Ticking {1}->{2}", Thread.CurrentThread.ManagedThreadId, this.tick, this.tick + 1);
                this.tick++;
                Monitor.PulseAll(this);
            }
        }

        public void WaitForTick(int tick)
        {
            lock (this)
            {
                Debug.WriteLine("{0}: Wait for tick {1}", Thread.CurrentThread.ManagedThreadId, tick);
                while (this.tick < tick)
                {
                    Monitor.Wait(this);
                }
                Debug.WriteLine("{0}: Received tick {1}", Thread.CurrentThread.ManagedThreadId, tick);
            }
        }
    }

    internal interface IClock
    {
        void WaitForTick(int tick);
    }

}